package com.kmxd.ams.app.arrange.command;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.kmxd.ams.app.event.AmsEventPublisher;
import com.kmxd.ams.client.archive.IArchiveRuleMainSvc;
import com.kmxd.ams.client.archive.IArchiveTypeSvc;
import com.kmxd.ams.client.archive.dto.RuleFieldQueryDTO;
import com.kmxd.ams.client.archive.vo.ArchiveRuleFieldListVO;
import com.kmxd.ams.client.arrange.IDocumentSvc;
import com.kmxd.ams.client.arrange.IProjectSvc;
import com.kmxd.ams.client.arrange.IVolumeSvc;
import com.kmxd.ams.client.arrange.dto.ChangeInVolumeArchCodeDTO;
import com.kmxd.ams.client.arrange.vo.ArchCodeVO;
import com.kmxd.ams.client.arrange.vo.DocumentChangeSortListVO;
import com.kmxd.ams.client.arrange.vo.OptionsVO;
import com.kmxd.ams.client.arrange.vo.VolumeChangeSortListVO;
import com.kmxd.ams.client.event.involume.ChangeInVolumeArchCodeEvent;
import com.kmxd.ams.client.event.log.DocumentLogEvent;
import com.kmxd.ams.client.event.log.VolumeLogEvent;
import com.kmxd.ams.core.common.constant.ArchConst;
import com.kmxd.ams.core.common.enums.ArchOpEnum;
import com.kmxd.ams.core.common.enums.RuleFieldTypeEnum;
import com.kmxd.ams.core.util.ConvertUtil;
import com.kmxd.ams.core.util.DynamicTableNameUtil;
import com.kmxd.ams.core.util.IdUtil;
import com.kmxd.ams.core.util.SecurityUtil;
import com.kmxd.ams.infra.archive.entity.ArchiveType;
import com.kmxd.ams.infra.arrange.base.BaseBO;
import com.kmxd.ams.infra.arrange.entity.Document;
import com.kmxd.ams.infra.arrange.entity.Project;
import com.kmxd.ams.infra.arrange.entity.Volume;
import com.kmxd.ams.infra.tmpl.dto.ArchTmplBO;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * 同分类调件
 *
 * @author nickbi
 */
@Slf4j
@Component
@AllArgsConstructor
public class ChangeSortCmd {
  private final IVolumeSvc volumeSvc;
  private final IDocumentSvc documentSvc;
  private final ArchCodeGenCmd archCodeGenCmd;
  private final IArchiveRuleMainSvc archiveRuleMainSvc;
  private final IProjectSvc projectSvc;
  private final IArchiveTypeSvc archiveTypeSvc;

  private final AmsEventPublisher amsEventPublisher;

  @Transactional(rollbackFor = Exception.class)
  public void changeSort(BaseBO req) {
    if (req.getTableId().contains(ArchTmplBO.VOLUME)) {
      changeVolume(req);
    }
    if (req.getTableId().contains(ArchTmplBO.DOCUMENT)) {
      changeDocument(req);
    }
  }

  public void changeVolume(BaseBO req) {
    DynamicTableNameUtil.setTableName(req.getTableId());
    List<Volume> volumeList = volumeSvc.selectListByIds(IdUtil.split(req.getVolIds()));
    int minSequence =
        volumeList.stream()
            .filter(vol -> Objects.nonNull(vol.getVolSequence()))
            .mapToInt(Volume::getVolSequence)
            .min()
            .orElse(1);
    if (ObjectUtil.isNotEmpty(req.getOrder())) {
      minSequence = NumberUtil.parseInt(req.getOrder());
    }
    AtomicInteger sequence = new AtomicInteger(minSequence);
    // 数据排序
    Ordering<Volume> ordering =
        Ordering.explicit(IdUtil.splitSortList(req.getVolIds())).onResultOf(Volume::getId);
    ImmutableList<Volume> sortList = ordering.immutableSortedCopy(volumeList);
    sortList.forEach(
        volume -> {
          Volume qryArchCode = ConvertUtil.toBean(volume, Volume.class);
          qryArchCode.setArchTypeId(req.getArchTypeId());
          qryArchCode.setTableId(req.getTableId());
          qryArchCode.setVolSequence(sequence.getAndIncrement());
          ArchCodeVO archCode =
              archCodeGenCmd.refreshArchCode(qryArchCode, SecurityUtil.getTokenValue());
          volume.setArchCode(archCode.getArchCode());
          volume.setVolSequence(archCode.getSequence());
          amsEventPublisher.publishEvent(
              new VolumeLogEvent(volume, ArchOpEnum.VOLUME_SORT, "同分类调卷"));
        });

    DynamicTableNameUtil.setTableName(req.getTableId());
    volumeSvc.updateBatchById(sortList);
    // 更新案卷下的文件档号信息
    archCodeGenCmd.refreshDocumentArchCode(
        new ChangeInVolumeArchCodeEvent(
            ChangeInVolumeArchCodeDTO.builder()
                .archTypeId(req.getArchTypeId())
                .volIds(req.getVolIds())
                .tableId(req.getTableId())
                .updateSequence(false)
                .build()));
  }

  public void changeDocument(BaseBO req) {
    DynamicTableNameUtil.setTableName(req.getTableId());
    List<Document> documentList = documentSvc.selectListByIds(req);
    int minSequence =
        documentList.stream()
            .filter(doc -> Objects.nonNull(doc.getDocSequence()))
            .mapToInt(Document::getDocSequence)
            .min()
            .orElse(1);
    if (ObjectUtil.isNotEmpty(req.getOrder())) {
      minSequence = NumberUtil.parseInt(req.getOrder());
    }
    AtomicInteger sequence = new AtomicInteger(minSequence);
    // 数据排序
    Ordering<Document> ordering =
        Ordering.explicit(IdUtil.splitSortList(req.getDocIds())).onResultOf(Document::getId);
    ImmutableList<Document> sortList = ordering.immutableSortedCopy(documentList);
    sortList.forEach(
        document -> {
          document.setTableId(req.getTableId());
          document.setArchTypeId(req.getArchTypeId());
          document.setDocSequence(sequence.getAndIncrement());
          ArchCodeVO archCode =
              archCodeGenCmd.refreshArchCode(document, SecurityUtil.getTokenValue());
          document.setArchCode(archCode.getArchCode());
          document.setDocSequence(archCode.getSequence());
          amsEventPublisher.publishEvent(
              new DocumentLogEvent(document, ArchOpEnum.VOLUME_SORT, "同分类调件"));
        });
    DynamicTableNameUtil.setTableName(req.getTableId());
    documentSvc.updateBatchById(sortList);
  }

  public DocumentChangeSortListVO changeSortList(Document req) {
    Document entity = documentSvc.view(req);
    entity.setArchTypeId(req.getArchTypeId());
    entity.setTableId(req.getTableId());
    RuleFieldQueryDTO qry = ConvertUtil.toBean(entity, RuleFieldQueryDTO.class);
    if (Objects.nonNull(entity.getProjId())) {
      qry.setHaveProject(Boolean.TRUE);
    }
    if (Objects.nonNull(entity.getVolId())) {
      qry.setHaveVol(Boolean.TRUE);
    }
    if (Objects.nonNull(entity.getBoxId())) {
      qry.setHaveBox(Boolean.TRUE);
    }
    // 卷内调序
    if (Objects.nonNull(entity.getVolId())) {
      Document qryDocument = new Document();
      qryDocument.setStatus(entity.getStatus());
      qryDocument.setArchTypeId(entity.getArchTypeId());
      qryDocument.setVolId(entity.getVolId());
      qryDocument.setTableId(entity.getTableId());
      List<Document> documentList = documentSvc.selectList(qryDocument);
      DocumentChangeSortListVO res = new DocumentChangeSortListVO();
      Map<String, Object> columeMap = new HashMap<>();
      columeMap.put(ArchConst.VOL_ID_DB, req.getVolId());
      Integer maxDocSequence = documentSvc.getMaxDocSequence(columeMap, req.getTableId(), true);
      res.setList(documentList);
      int minSequence = Optional.ofNullable(maxDocSequence).orElse(1);

      res.setMinSequence(minSequence);
      return res;
    } else {
      Map<String, Object> documentMap = BeanUtil.beanToMap(entity);
      setRuleByProjId(entity.getArchTypeId(), documentMap.get(ArchConst.PROJ_ID), qry);
      // 按规则获取同分类数据信息
      List<ArchiveRuleFieldListVO> ruleFieldByVersion =
          archiveRuleMainSvc.getRuleFieldByVersion(qry);
      List<ArchiveRuleFieldListVO> filterRule =
          ruleFieldByVersion.stream()
              .filter(rule -> rule.getEnableGroup().equals(Boolean.TRUE))
              .filter(rule -> !rule.getFieldCode().contains(ArchConst.SEQUENSE))
              .collect(Collectors.toList());
      Map<String, Object> qryData = getQueryDataWithRule(filterRule, documentMap);
      Document qryDocument = ConvertUtil.toBean(qryData, Document.class);
      qryDocument.setStatus(entity.getStatus());
      qryDocument.setArchTypeId(entity.getArchTypeId());
      qryDocument.setTableId(entity.getTableId());
      List<Document> documentList = documentSvc.selectList(qryDocument);

      DocumentChangeSortListVO res = new DocumentChangeSortListVO();
      // 构建同分类规则对应的数据信息，用于提示用户当前的分组信息
      if (ObjectUtil.isNotEmpty(documentList)) {
        List<OptionsVO> options =
            filterRule.stream()
                .map(
                    rule -> {
                      String fieldCode = StringUtils.underlineToCamel(rule.getFieldCode());
                      OptionsVO vo = new OptionsVO();
                      vo.setLabel(rule.getFieldName());
                      vo.setValue(String.valueOf(qryData.get(fieldCode)));
                      return vo;
                    })
                .collect(Collectors.toList());
        res.setOptions(options);
      }
      res.setList(documentList);

      Map<String, Object> columnMap = new HashMap<>();
      // 生成拼接部分内容
      ruleFieldByVersion.stream()
          .filter(ArchiveRuleFieldListVO::getEnableGroup)
          .filter(f -> !f.getFieldCode().contains(ArchConst.SEQUENSE))
          .forEach(
              item -> {
                if (!NumberUtil.equals(RuleFieldTypeEnum.FIXED.getCode(), item.getType())) {
                  JSONObject jsonObject = JSONUtil.parseObj(entity);
                  String fieldKey =
                      !StringUtils.isCamel(item.getFieldCode())
                          ? StringUtils.underlineToCamel(item.getFieldCode())
                          : item.getFieldCode();
                  String value = jsonObject.getStr(fieldKey);
                  if (StrUtil.isNotBlank(value)) {
                    columnMap.put(item.getFieldCode(), value);
                  }
                }
              });

      Integer maxDocSequence = documentSvc.getMaxDocSequence(columnMap, req.getTableId(), true);

      int minSequence = Optional.ofNullable(maxDocSequence).orElse(1);
      res.setMinSequence(minSequence);
      return res;
    }
  }

  private Map<String, Object> getQueryDataWithRule(
      List<ArchiveRuleFieldListVO> ruleList, Map<String, Object> documentMap) {
    return ruleList.stream()
        .map(
            field -> {
              String filedName = StringUtils.underlineToCamel(field.getFieldCode());
              Object value = documentMap.get(filedName);
              Map<String, Object> map = Maps.newHashMap();
              map.put(filedName, value);
              return map;
            })
        .flatMap(map -> map.entrySet().stream())
        .collect(HashMap::new, (m, v) -> m.put(v.getKey(), v.getValue()), HashMap::putAll);
  }

  public VolumeChangeSortListVO changeSortList(Volume req) {
    Volume entity = volumeSvc.view(req);
    entity.setArchTypeId(req.getArchTypeId());
    entity.setTableId(req.getTableId());
    RuleFieldQueryDTO qry = ConvertUtil.toBean(entity, RuleFieldQueryDTO.class);
    if (Objects.nonNull(entity.getProjId())) {
      qry.setHaveProject(Boolean.TRUE);
    }
    if (Objects.nonNull(entity.getVolId())) {
      qry.setHaveVol(Boolean.TRUE);
    }
    if (Objects.nonNull(entity.getBoxId())) {
      qry.setHaveBox(Boolean.TRUE);
    }

    Map<String, Object> volumeMap = BeanUtil.beanToMap(entity);
    setRuleByProjId(entity.getArchTypeId(), entity.getProjId(), qry);
    // 按规则获取同分类数据信息
    List<ArchiveRuleFieldListVO> ruleFieldByVersion = archiveRuleMainSvc.getRuleFieldByVersion(qry);
    List<ArchiveRuleFieldListVO> filterRule =
        ruleFieldByVersion.stream()
            .filter(rule -> rule.getEnableGroup().equals(Boolean.TRUE))
            .filter(rule -> !rule.getFieldCode().contains(ArchConst.SEQUENSE))
            .collect(Collectors.toList());
    Map<String, Object> qryData = getQueryDataWithRule(filterRule, volumeMap);
    Volume qryVolume = ConvertUtil.toBean(qryData, Volume.class);
    qryVolume.setStatus(entity.getStatus());
    qryVolume.setArchTypeId(entity.getArchTypeId());
    qryVolume.setTableId(entity.getTableId());
    List<Volume> volumeList = volumeSvc.selectList(qryVolume);
    VolumeChangeSortListVO res = new VolumeChangeSortListVO();
    // 构建同分类规则对应的数据信息，用于提示用户当前的分组信息
    if (ObjectUtil.isNotEmpty(volumeList)) {
      List<OptionsVO> options =
          filterRule.stream()
              .map(
                  rule -> {
                    String fieldCode = StringUtils.underlineToCamel(rule.getFieldCode());
                    OptionsVO vo = new OptionsVO();
                    vo.setLabel(rule.getFieldName());
                    vo.setValue(String.valueOf(qryData.get(fieldCode)));
                    return vo;
                  })
              .collect(Collectors.toList());

      Map<String, Object> columnMap = new HashMap<>();
      // 生成拼接部分内容
      ruleFieldByVersion.stream()
          .filter(ArchiveRuleFieldListVO::getEnableGroup)
          .filter(f -> !f.getFieldCode().contains(ArchConst.SEQUENSE))
          .forEach(
              item -> {
                if (!NumberUtil.equals(RuleFieldTypeEnum.FIXED.getCode(), item.getType())) {
                  JSONObject jsonObject = JSONUtil.parseObj(entity);
                  String fieldKey =
                      !StringUtils.isCamel(item.getFieldCode())
                          ? StringUtils.underlineToCamel(item.getFieldCode())
                          : item.getFieldCode();
                  String value = jsonObject.getStr(fieldKey);
                  if (StrUtil.isNotBlank(value)) {
                    columnMap.put(item.getFieldCode(), value);
                  }
                }
              });

      String maxDocSequence = volumeSvc.getSequence(columnMap, req.getTableId(), true);
      int sequence = NumberUtil.parseInt(maxDocSequence);
      int minSequence = Optional.ofNullable(sequence).orElse(1);
      res.setMinSequence(minSequence);
      res.setOptions(options);
    }
    res.setList(volumeList);
    return res;
  }

  private void setRuleByProjId(Long archTypeId, Object projId, RuleFieldQueryDTO fieldQueryDTO) {
    if (Objects.nonNull(projId)) {
      ArchiveType archiveType = archiveTypeSvc.getById(archTypeId);
      if (Objects.isNull(archiveType)) {
        return;
      }
      Project qry = new Project();
      qry.setArchTypeId(archiveType.getId());
      qry.setId(Long.parseLong(String.valueOf(projId)));
      qry.setTableId(archiveType.getProjTableId());
      Project project = projectSvc.view(qry);
      fieldQueryDTO.setRuleId(project.getArchRuleId());
    }
  }
}
